#include <stdlib.h>
#include "byte_code.h"
#include "obj.h"
#include "vm.h"

static bool isTrue(Value* val);
static uint8_t readByte(VM* vm);
static uint16_t readShort(VM* vm);

void vmInit(VM* vm, ObjFunction* fun) {
    stack_init(&vm->stack, sizeof(Value));
    hashTableInit(&vm->global, objStrHash, objStrCompare);
    vm->frames = malloc(sizeof(CallFrame) * VM_FRAME_MAX_COUNT);
    vm->currentFrameIdx = 0;
    vm->frames->ebp = 0;
    vm->frames->ip = 0;
    vm->frames->fun = fun;
}

void run(VM* vm) {
    for(;;vm->frames[vm->currentFrameIdx].ip++){
        Code code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, vm->frames[vm->currentFrameIdx].ip);
        // printf("ip: %d, stack size: %d, code: %d, top: %f\n", 
        //     vm->frames[vm->currentFrameIdx].ip, vm->stack.size, code.code, chunkVal2num(*(Value*)stack_peek(&vm->stack)));
        Value val;
        switch (code.code)
        {
        case OP_RETURN: {
            // printf("OP_RETURN: stack size: %d\n", vm->stack.size);
            if(vm->currentFrameIdx > 0) {
                Value* val = stack_pop(&vm->stack);
                vm->stack.size -= vm->frames[vm->currentFrameIdx].fun->arity + 1;
                stack_push(&vm->stack, val);
                vm->currentFrameIdx--;
                continue;
            }
            return;
        }
        case OP_CALL: {
            uint8_t args = readByte(vm);
            Value* val = stack_get(&vm->stack, vm->stack.size - 1 - args);
            callFrameInit(vm->frames + ++vm->currentFrameIdx, val->val.obj, vm->stack.size - args);
            break;
        }
        case OP_CONSTANT:
            code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
            val = chunkGetConstant(&vm->frames[vm->currentFrameIdx].fun->chunk, code.code);
            stack_push(&vm->stack, &val);
            // printf("OP_CONSTANT val: %.2f, stack size: %d\n", chunkVal2num(val), vm->stack.size);
            break;
        case OP_ADD:
            Value* val2 = stack_pop(&vm->stack);
            Value* val1 = stack_pop(&vm->stack);
            double num1 = chunkVal2num(*val1);
            double num2 = chunkVal2num(*val2);
            double res = num1 + num2;
            Value valAdd = chunkNum2Val(res);
            stack_push(&vm->stack, &valAdd);
            // printf("OP_ADD val1: %.2f, val2: %.2f, res: %.2f, stack size: %d\n", num1, num2, res, vm->stack.size);
            break;
        case OP_SUBSTRACT:
            val2 = stack_pop(&vm->stack);
            val1 = stack_pop(&vm->stack);
            num1 = chunkVal2num(*val1);
            num2 = chunkVal2num(*val2);
            res = num1 - num2;
            Value valSub = chunkNum2Val(res);
            stack_push(&vm->stack, &valSub);
            // printf("OP_SUBSTRACT val1: %.2f, val2: %.2f, res: %.2f, stack size: %d\n", num1, num2, res, vm->stack.size);
            break;
        case OP_MULTIPLY:
            val2 = stack_pop(&vm->stack);
            val1 = stack_pop(&vm->stack);
            num1 = chunkVal2num(*val1);
            num2 = chunkVal2num(*val2);
            res = num1 * num2;
            Value valMul = chunkNum2Val(res);
            stack_push(&vm->stack, &valMul);
            // printf("OP_MULTIPLY val1: %.2f, val2: %.2f, res: %.2f, stack size: %d\n", num1, num2, res, vm->stack.size);
            break;
        case OP_DIVIDE:
            val2 = stack_pop(&vm->stack);
            val1 = stack_pop(&vm->stack);
            num1 = chunkVal2num(*val1);
            num2 = chunkVal2num(*val2);
            res = num1 / num2;
            Value valDiv = chunkNum2Val(res);
            stack_push(&vm->stack, &valDiv);
            // printf("OP_DIVIDE val1: %.2f, val2: %.2f, res: %.2f, stack size: %d\n", num1, num2, res, vm->stack.size);
            break;
        case OP_NEGATE:
            val1 = stack_pop(&vm->stack);
            res = -chunkVal2num(*val1);
            Value valNeg = chunkNum2Val(res);
            stack_push(&vm->stack, &valNeg);
            // printf("OP_NEGATE val1: %.2f, res: %.2f, stack size: %d\n", chunkVal2num(*val1), res, vm->stack.size);
            break;
        case OP_POP:
            stack_pop(&vm->stack);
            break;
        case OP_PRINT:
            Value* printVal = stack_pop(&vm->stack);
            if(printVal->type == VAL_NUMBER) {
                double printNum = chunkVal2num(*printVal);
                printf("%f\n", printNum);
            } else if(printVal->type == VAL_STRING) {
                ObjString* str = printVal->val.obj;
                printf("%s\n", str->val);
            }
            break;
        case OP_NIL:
            Value* pushVal = malloc(sizeof(Value));
            pushVal->type = VAL_NULL;
            stack_push(&vm->stack, pushVal);
            break;
        case OP_ASSIGNMENT:
        case OP_VARIABLE_DECLARE:
            // printf("vm OP_VARIABLE_DECLARE\n");
            code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
            val = chunkGetConstant(&vm->frames[vm->currentFrameIdx].fun->chunk, code.code);
            Value* assignVal = stack_peek(&vm->stack);
            // printf("vm OP_VARIABLE_DECLARE valtype: %d, valname: %s, t1: %d\n", 
            // val.type, ((ObjString*)val.val.obj)->val, assignVal->type);
            hashTablePut(&vm->global, val.val.obj, assignVal);
            // printf("vm OP_VARIABLE_DECLARE end\n");
            break;
        case OP_VARIABLE_GET:
            // printf("vm OP_VARIABLE_GET\n");
            code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
            val = chunkGetConstant(&vm->frames[vm->currentFrameIdx].fun->chunk, code.code);
            Entry* valGetEntry = hashTableGetEntry(&vm->global, val.val.obj);
            // printf("valGetVal: %d, key: %p\n", valGetVal->type, valGetVal->val.obj);
            // if(valGetVal->type == VAL_STRING)
            //     printf("valGetVal: %s\n", ((ObjString*)valGetVal->val.obj)->val);
            if(valGetEntry->key == NULL) {
                printf("error: 全局变量 %s 没有定义！\n", ((ObjString*)val.val.obj)->val);
                exit(1);
            } else
                stack_push(&vm->stack, valGetEntry->val);
            // printf("vm OP_VARIABLE_GET end\n");
            break;
        case OP_LOCAL_GET:
            // printf("OP_LOCAL_GET\n");
            code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
            stack_push(&vm->stack, stack_get(&vm->stack, vm->frames[vm->currentFrameIdx].ebp + code.code));
            break;
        case OP_LOCAL_ASSIGN:
            code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
            stack_set(&vm->stack, stack_peek(&vm->stack), code.code);
            break;
        case OP_JMP_IF_FALSE: {
            uint16_t jmpSteps = readShort(vm);
            Value* val = stack_peek(&vm->stack);
            if(!isTrue(val)) {
                vm->frames[vm->currentFrameIdx].ip += jmpSteps;
            }
            break;
        }
        case OP_JMP_IF_TRUE: {
            uint16_t jmpSteps = readShort(vm);
            Value* val = stack_peek(&vm->stack);
            if(isTrue(val)) {
                vm->frames[vm->currentFrameIdx].ip += jmpSteps;
            }
            break;
        }
        case OP_JMP: {
            uint16_t jmpSteps = readShort(vm);
            // ip = jmpPositon; ip + jmpSteps = chunk.codes.size - 1;
            vm->frames[vm->currentFrameIdx].ip += jmpSteps;
            break;
        }
        case OP_LOOP: {
            uint16_t jmpSteps = readShort(vm);
            // ip = chunk.codes.size - 1; ip - jmpSteps = loopStart - 1;
            vm->frames[vm->currentFrameIdx].ip -= jmpSteps;
            break;
        }
        case OP_LESS:
            val2 = stack_pop(&vm->stack);
            val1 = stack_pop(&vm->stack);
            if(val1->type != VAL_NUMBER || val2->type != VAL_NUMBER){
                printf("error: 只能比较数字");
                exit(1);
            }
            bool cmpRes = val1->val.num < val2->val.num;
            val.type = VAL_NUMBER;
            val.val.num = cmpRes;
            stack_push(&vm->stack, &val);
            break;
        case OP_GREATER:
            val2 = stack_pop(&vm->stack);
            val1 = stack_pop(&vm->stack);
            if(val1->type != VAL_NUMBER || val2->type != VAL_NUMBER){
                printf("error: 只能比较数字");
                exit(1);
            }
            cmpRes = val1->val.num > val2->val.num;
            val.type = VAL_NUMBER;
            val.val.num = cmpRes;
            stack_push(&vm->stack, &val);
            break;
        case OP_EQUAL:
            val2 = stack_pop(&vm->stack);
            val1 = stack_pop(&vm->stack);
            if(val1->type != VAL_NUMBER || val2->type != VAL_NUMBER){
                printf("error: 只能比较数字");
                exit(1);
            }
            cmpRes = val1->val.num == val2->val.num;
            val.type = VAL_NUMBER;
            val.val.num = cmpRes;
            stack_push(&vm->stack, &val);
            break;
        case OP_NOT:
            val1 = stack_pop(&vm->stack);
            cmpRes = !isTrue(val1);
            val.type = VAL_NUMBER;
            val.val.num = cmpRes;
            stack_push(&vm->stack, &val);
            break;
        default:
            break;
        }
    }
}

static bool isTrue(Value* conditionVal) {
    if(conditionVal->type != VAL_NULL
    && (conditionVal->type == VAL_NUMBER && conditionVal->val.num != 0
    || conditionVal->type == VAL_STRING && *((ObjString*)conditionVal->val.obj)->val)) {
        return true;
    }
    return false;
}

static uint8_t readByte(VM* vm) {
    Code code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
    return code.code;
}

static uint16_t readShort(VM* vm) {
    Code code = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
    Code code1 = chunkGetCode(&vm->frames[vm->currentFrameIdx].fun->chunk, ++vm->frames[vm->currentFrameIdx].ip);
    return (code.code << 8) | code1.code;
}
